/**
 *       Copyright (c) 2012 Lukas Zaruba
 *
 *   This file is part of Robot Playground.
 *
 *   Robot Playground is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU Lesser General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   Robot Playground is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU Lesser General Public License for more details.
 *
 *   You should have received a copy of the GNU Lesser General Public License
 *   along with Robot Playground. If not, see <http://www.gnu.org/licenses/>.
 */
package net.zarubsys.robotplayground.core.session;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.zarubsys.robotplayground.core.Environment;
import net.zarubsys.robotplayground.core.device.IDevice;
import net.zarubsys.robotplayground.core.device.IDeviceConfiguration;
import net.zarubsys.robotplayground.core.device.NullDevice;
import net.zarubsys.robotplayground.core.modules.IModule;
import net.zarubsys.robotplayground.core.modules.IModuleConfiguration;
import net.zarubsys.robotplayground.core.monitors.IMonitor;
import net.zarubsys.robotplayground.core.program.API;
import net.zarubsys.robotplayground.core.program.IProgram;
import net.zarubsys.robotplayground.core.program.IProgramConfiguration;
import net.zarubsys.robotplayground.core.program.ProgramRunnable;

import org.apache.log4j.MDC;

/**
 * Session
 * 
 * Use only documented methods!
 *
 * @author Lukas Zaruba, lukas.zaruba@gmail.com
 */
public class Session implements Serializable {
	
	private static final long serialVersionUID = -5129852432602038041L;
	
	private String name;
	private String description;
	private Date lastRun;
	
	private List<DeviceSessionContainer> devices = new ArrayList<>();
	private List<ModuleSessionContainer> modules = new ArrayList<>();
	private List<String> monitors = new ArrayList<>();
	private List<ProgramSessionContainer> programs = new ArrayList<>();
	
	public Session() {
		
	}

	/**
	 * @return name of this session
	 */
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	/**
	 * @return description of this session
	 */
	public String getDescription() {
		return description;
	}

	public void setDescription(String description) {
		this.description = description;
	}

	public Date getLastRun() {
		return lastRun;
	}

	public void setLastRun(Date lastRun) {
		this.lastRun = lastRun;
	}

	/**
	 * @return list of devices associated with this session
	 * together with the configuration for each of them.
	 * 
	 * Use this list for adding of new devices
	 */
	public List<DeviceSessionContainer> getDevices() {
		return devices;
	}

	public void setDevices(List<DeviceSessionContainer> devices) {
		this.devices = devices;
	}

	/**
	 * @return list of modules associated with this session
	 * together with the configuration for each of them.
	 * 
	 * Use this list for adding of new modules
	 */
	public List<ModuleSessionContainer> getModules() {
		return modules;
	}

	public void setModules(List<ModuleSessionContainer> modules) {
		this.modules = modules;
	}

	/**
	 * @return list of ids of monitors associated with this session
	 * 
	 * Use this list for adding of new monitors
	 */
	public List<String> getMonitors() {
		return monitors;
	}

	public void setMonitors(List<String> monitors) {
		this.monitors = monitors;
	}

	/**
	 * @return list of programs associated with this session
	 * together with the configuration for each of them.
	 * 
	 * Use this list for adding of new programs
	 */
	public List<ProgramSessionContainer> getPrograms() {
		return programs;
	}

	public void setPrograms(List<ProgramSessionContainer> programs) {
		this.programs = programs;
	}
	
	public RunningSession prepareRunningSession(String logPath) {
		Environment env = new Environment(logPath);
		
		MDC.put(Environment.LOG_PATH_KEX, logPath);
		
		Map<String, IDevice<? extends IDeviceConfiguration>> initiatedDevices = new HashMap<>();
		List<IDevice<IDeviceConfiguration>> deviceInstances = new ArrayList<>();
		for (DeviceSessionContainer deviceCont : devices) {
			IDevice<IDeviceConfiguration> device = SessionsHelper.getInstance().getDevice(deviceCont.getDeviceId());
			if (device == null) {
				throw new IllegalStateException("There is no device for id '" + deviceCont.getDeviceId() + "' registered!");
			}
			device.init(deviceCont.getConfiguration(), env);
			initiatedDevices.put(device.getId(), device);
			deviceInstances.add(device);
		}
		
		List<IModule<? extends IDevice, ? extends IModuleConfiguration>> moduleInstances = new ArrayList<>();
		Map<String, IModule<? extends IDevice, ? extends IModuleConfiguration>> initiatedModules = new HashMap<>();
		for (ModuleSessionContainer moduleCont : modules) {
			IModule<IDevice<?>, IModuleConfiguration> module = SessionsHelper.getInstance().getModule(moduleCont.getModuleId());
			if (module == null) {
				throw new IllegalStateException("There is no module for id '" + moduleCont.getModuleId() + "' registered!");
			}
			String requiredDevice = module.getRequiredDevice();
			IDevice<? extends IDeviceConfiguration> device = NullDevice.getInstance(); 
			if (!NullDevice.ID.equals(requiredDevice)) {
				device = initiatedDevices.get(requiredDevice);
				if (device == null) {
					throw new IllegalStateException("There is no device for id '" + requiredDevice + "' in the list of devices of this session (required by module '" + 
						module.getId() + "'!");
				}
			}
			module.init(device, moduleCont.getConfiguration(), env);
			initiatedModules.put(module.getId(), module);
			moduleInstances.add(module);
		}
		
		List<IMonitor> monitorInstances = new ArrayList<>();
		Map<String, IMonitor> initiatedMonitors = new HashMap<>();
		for (String monitorId : monitors) {
			IMonitor monitor = SessionsHelper.getInstance().getMonitor(monitorId);
			List<String> requiredModuleIds = monitor.getRequiredModules();
			List<IModule<? extends IDevice, ? extends IModuleConfiguration>> requiredModules = new ArrayList<>();
			for (String requiredModuleId : requiredModuleIds) {
				IModule<? extends IDevice, ? extends IModuleConfiguration> requiredModule = initiatedModules.get(requiredModuleId);
				if (requiredModule == null) {
					throw new IllegalStateException("There is no module for id '" + requiredModuleId + "' in the list of modules of this session (required by monitor '" + 
							monitor.getId() + "'!");
				}
				requiredModules.add(requiredModule);
			}
			monitor.init(requiredModules, env);
			initiatedMonitors.put(monitor.getId(), monitor);
			monitorInstances.add(monitor);
		}
		
		API api = new API(initiatedModules.values(), initiatedMonitors.values(), env);
		
		List<ProgramRunnable> programRunnables = new ArrayList<>();
		for (ProgramSessionContainer programCont : programs) {
			IProgram<IProgramConfiguration> program = SessionsHelper.getInstance().getProgram(programCont.getProgramId());
			programRunnables.add(new ProgramRunnable(program, api, programCont.getConfiguration(), env));
		}
		
		RunningSession runningSession = new RunningSession(api, deviceInstances, moduleInstances, monitorInstances, programRunnables, this);
		return runningSession;
	}
	
}

